package net.zhuike.novel.app.ui.utils;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import net.zhuike.novel.app.bean.BookChapterBean;
import net.zhuike.novel.app.bean.CollBookBean;
import net.zhuike.novel.app.bean.local.BookRepository;
import net.zhuike.novel.app.service.BookReadService;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import framework.common.zanetwork.UeHttpUrl;
import framework.utils.StringsUtil;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * 1. 上下文Context的关键字：parent：父context、input：输入的url、_input：做compute时的输入源。
 * 2. 上下文Context中的value的类型：Object[]、JSON、Map，其中Object[]种的元素类型：String、String[]
 */
public class SiteHelper {

    private JSONObject json;
    private OkHttpClient mOkHttpClient;

    public SiteHelper(JSONObject json) {
        this.json = json;
    }

    public SiteHelper() {
    }

    public Map<String, Object> executeMulu(Map<String, Object> parent, String startUrl) throws Exception {
        return executes("mulu", parent, startUrl);
    }

    public Map<String, Object> executeChapter(Map<String, Object> parent, String startUrl) throws Exception {
        return execute("chapter", parent, startUrl);
    }

    public Map<String, Object> execute(String name, Map<String, Object> parent, String startUrl) throws Exception {
        if (!json.containsKey(name)) {
            throw new IllegalArgumentException(String.format("name=[%s]", name));
        }

        Map<String, Object> context = new HashMap<String, Object>();
        if (parent != null) {
            context.put("parent", parent);
        }
        context.put("input", startUrl);

        JSONObject root = json.getJSONObject(name);
        for (int i = 1; i < 10; i++) {
            JSONObject iPage = root.getJSONObject("page" + i);
            if (iPage == null) break;

            JSONObject inputJson = iPage.getJSONObject("input");
            if (inputJson != null) {
                requestInput(inputJson, context);
            } else {
            }

            List<String> properties = new ArrayList<String>(iPage.keySet());
            Comparator4JSONObject comparator = new Comparator4JSONObject(iPage);
            Collections.sort(properties, comparator);
            for (String property : properties) {
                JSONObject po = iPage.getJSONObject(property);
                String type = po.getString("type");
                if (type == null || !type.startsWith("regex")) continue;
                Object result = processRegex(po, context);
                if (result != null) {
                    context.put(property, result);
                }
            }

            for (String property : properties) {
                JSONObject po = iPage.getJSONObject(property);
                if (!"compute".equals(po.getString("type"))) continue;
                Object result = processCompute(po, context);
                if (result != null) {
                    context.put(property, result);
                }
            }
        }

        return context;
    }

    public Map<String, Object> executes(String name, Map<String, Object> parent, String startUrl) throws Exception {
        if (!json.containsKey(name)) {
            throw new IllegalArgumentException(String.format("name=[%s]", name));
        }

        Map<String, Object> context = new HashMap<String, Object>();
        if (parent != null) {
            context.put("parent", parent);
        }
        context.put("input", startUrl);

        JSONObject root = json.getJSONObject(name);
        for (int i = 1; i < 10; i++) {
            JSONObject iPage = root.getJSONObject("page" + i);
            if (iPage == null) break;

            JSONObject inputJson = iPage.getJSONObject("input");
            if (inputJson != null) {
                JSONObject headers = inputJson.getJSONObject("headers");
                try {
                    mOkHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(30, TimeUnit.SECONDS)       //设置连接超时
                            .readTimeout(60, TimeUnit.SECONDS)          //设置读超时
                            .writeTimeout(60, TimeUnit.SECONDS)          //设置写超时
                            .retryOnConnectionFailure(true)             //是否自动重连
                            .build();

                    Request.Builder builder = new Request.Builder();
                    if (headers != null) {
                        Set<String> names = headers.keySet();
                        for (String kname : names) {
                            builder.addHeader("name", getStringValue(context, headers.getString(kname)));
                        }
                    }
                    byte[] postData = null;
                    if ("post".equalsIgnoreCase(inputJson.getString("method"))) {
                        if (inputJson.containsKey("postData")) {
                            JSONArray postDataArray = inputJson.getJSONArray("postData");

                            builder.addHeader("Content-Type", "application/x-www-form-urlencoded");

                            StringBuilder buffer = new StringBuilder();
                            for (int j = 0; j < postDataArray.size(); j += 2) {
                                if (j > 0) {
                                    buffer.append('&');
                                }
                                buffer.append(String.format("%s=%s", postDataArray.getString(j), postDataArray.getString(j + 1)));
                            }
                            postData = buffer.toString().getBytes("utf-8");//得到实体数据
                            builder.addHeader("Content-Length", String.valueOf(postData.length));
                        }
                        builder.url(getStringValue(context, inputJson.getString("url")));
                        builder.post(buildRequestBody(context));
                    } else {
                        builder.url(getStringValue(context, inputJson.getString("url")));
                        builder.get();
                    }
                    final Request request = builder
                            .build();
                    Call call = mOkHttpClient.newCall(request);
                    call.enqueue(new Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {

                        }

                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String str = response.body().string();
                            context.put(inputJson.getString("outputVariableName"), str);
                            List<String> properties = new ArrayList<String>(iPage.keySet());
                            Comparator4JSONObject comparator = new Comparator4JSONObject(iPage);
                            Collections.sort(properties, comparator);
                            for (String property : properties) {
                                JSONObject po = iPage.getJSONObject(property);
                                String type = po.getString("type");
                                if (type == null || !type.startsWith("regex")) continue;
                                Object result = processRegex(po, context);
                                if (result != null) {
                                    context.put(property, result);
                                }
                            }

                            for (String property : properties) {
                                JSONObject po = iPage.getJSONObject(property);
                                if (!"compute".equals(po.getString("type"))) continue;
                                Object result = processCompute(po, context);
                                if (result != null) {
                                    context.put(property, result);
                                }
                            }
                        }
                    });
                } catch (Exception e) {
                }
            } else {
            }


        }
        return context;
    }

    private void requestInput(JSONObject inputJson, Map<String, Object> context) throws Exception {
        HttpURLConnection conn = null;
        String url = getStringValue(context, inputJson.getString("url"));
        URL realUrl = new URL(url);
        conn = (HttpURLConnection) realUrl.openConnection();
        conn.setReadTimeout(5000);
        conn.setConnectTimeout(5000);
        JSONObject headers = inputJson.getJSONObject("headers");
        if (headers != null) {
            Set<String> names = headers.keySet();
            for (String name : names) {
                conn.setRequestProperty(name, getStringValue(context, headers.getString(name)));
            }
        }
        byte[] postData = null;
        if ("post".equalsIgnoreCase(inputJson.getString("method"))) {
            conn.setRequestMethod("POST");
            if (inputJson.containsKey("postData")) {
                JSONArray postDataArray = inputJson.getJSONArray("postData");
                conn.setDoOutput(true);
                if (conn.getRequestProperty("Content-Type") == null) {
                    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                }
                StringBuilder buffer = new StringBuilder();
                for (int i = 0; i < postDataArray.size(); i += 2) {
                    if (i > 0) {
                        buffer.append('&');
                    }
                    buffer.append(String.format("%s=%s", postDataArray.getString(i), postDataArray.getString(i + 1)));
                }
                postData = buffer.toString().getBytes("utf-8");//得到实体数据
                conn.setRequestProperty("Content-Length", String.valueOf(postData.length));
            }
        }
        conn.connect();
        if (postData != null) {
            OutputStream out = null;
            try {
                out = conn.getOutputStream();
                out.write(postData);
                out.flush();
            } finally {
                //CloseableUtil.close(out);
            }
        }
        int statusCode = conn.getResponseCode();
        if (statusCode >= 400) {
            throw new IllegalArgumentException(String.format("bad request, code=%s", statusCode));
        }
        ByteArrayOutputStream baos = null;
        InputStream in = null;
        try {
            in = conn.getInputStream();
            if (in == null) return;
            if ("gzip".equalsIgnoreCase(conn.getContentEncoding())) {
                in = new GZIPInputStream(in);
            }
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[10240];
            while (true) {
                int size = in.read(buffer);
                if (size > 0) {
                    baos.write(buffer, 0, size);
                } else if (size == -1) {
                    break;
                }
            }
            byte[] bs = baos.toByteArray();
            String contentType = conn.getHeaderField("Content-Type");
            String encoding = null;
            if (contentType != null && !"".equals(contentType)) {
                int index = contentType.indexOf("=");
                if (index > -1) {
                    encoding = contentType.substring(index + 1);
                }
            }
            if (StringsUtil.isBlank(encoding)) {
                encoding = inputJson.getString("encoding");
            }
            if (StringsUtil.isBlank(encoding)) {
                encoding = "gbk";
            }
            String content = new String(bs, encoding);
            context.put(inputJson.getString("outputVariableName"), content);
        } finally {
            if (in != null) try {
                in.close();
            } catch (Exception err) {
            }
            ;
            if (baos != null) try {
                baos.close();
            } catch (Exception err) {
            }
            ;
            conn.disconnect();
        }
    }

    private Object processRegex(JSONObject regexJson, Map<String, Object> context) {
        String targetKey = regexJson.getString("target");
        if (isBlank(targetKey)) return null;
        Object targetValue = getValue(context, targetKey);
        if (targetValue == null) return null;
        if (targetValue.getClass().isArray()) {
            Object[] realTargetValue = (Object[]) targetValue;
            Object[] result = new Object[realTargetValue.length];
            for (int i = 0; i < realTargetValue.length; i++) {
                result[i] = executeRegex(regexJson, realTargetValue[i].toString());
            }
            return result;
        } else if (targetValue.getClass() == String.class) {
            return executeRegex(regexJson, (String) targetValue);
        } else {
            throw new IllegalArgumentException(String.format("unsupport type[%s].", targetValue.getClass().getName()));
        }
    }

    private Object executeRegex(JSONObject regexJson, String target) {
        String regex = regexJson.getString("regex");
        String type = regexJson.getString("type");
        if (type.startsWith("regex-group")) {
            String[] result = REUtil.groups(regex, target);
            if (result == null || result.length == 0) return null;
            if ("regex-group".equals(type)) {
                String content = doFilters(result[0].toString(), regexJson);
                return content;
            } else {
                return result;
            }
        } else if ("regex-match".equals(type)) {
            return REUtil.match(regex, target);
        } else if ("regex-matchs".equals(type)) {
            return REUtil.matchs(regex, target);
        } else {
            throw new IllegalArgumentException(String.format("wrong regex type[%s]", type));
        }
    }

    private String doFilters(String target, JSONObject json) {
        if (!json.containsKey("filters")) return target;
        JSONArray filters = json.getJSONArray("filters");
        for (int i = 0; i < filters.size(); i += 2) {
            target = target.replaceAll(filters.getString(i), filters.getString(i + 1));
        }

        return target;
    }

    private Object processCompute(JSONObject computeJson, Map<String, Object> context) {
        String targetKey = computeJson.getString("target");
        if (isBlank(targetKey)) return null;
        Object targetValue = getValue(context, targetKey);
        if (targetValue == null) return null;
        if (targetValue.getClass().isArray()) {
            Object[] realTargetValue = (Object[]) targetValue;
            Object[] result = new Object[realTargetValue.length];
            for (int i = 0; i < realTargetValue.length; i++) {
                try {
                    context.put("_input", realTargetValue[i]);
                    result[i] = parseJson(computeJson, context);
                } finally {
                    context.remove("_input");
                }
            }
            return result;
        } else if (targetValue.getClass() == String.class) {
            try {
                context.put("_input", targetValue);
                return parseJson(computeJson, context);
            } finally {
                context.remove("_input");
            }
        } else {
            throw new IllegalArgumentException(String.format("unsupport type[%s].", targetValue.getClass().getName()));
        }

    }

    private JSONObject parseJson(JSONObject computeJson, Map<String, Object> context) {
        Set<String> names = computeJson.keySet();

        JSONObject result = new JSONObject();
        for (String name : names) {
            Object value = computeJson.get(name);
            if (value.getClass() != JSONObject.class) continue;
            JSONObject json = (JSONObject) value;
            if (json.containsKey("regex")) {
                String target = getStringValue(context, json.getString("target"));
                if ("boolean".equalsIgnoreCase(json.getString("type"))) {
                    String matchedValue = REUtil.match(json.getString("regex"), target.toString());
                    boolean v = !StringsUtil.isBlank(matchedValue) && !"false".equalsIgnoreCase(matchedValue);
                    result.put(name, v);
                } else {
                    String matchedValue = "";
                    if (json.containsKey("groupIndex")) {
                        matchedValue = REUtil.group(json.getString("regex"), target.toString(), json.getIntValue("groupIndex"));
                    } else {
                        matchedValue = REUtil.match(json.getString("regex"), target.toString());
                    }
                    matchedValue = doFilters(decode(json.getString("decode"), matchedValue), json);
                    result.put(name, matchedValue);
                }
            } else if (json.containsKey("set")) {
                String setvalue = getStringValue(context, json.getString("set"));
                if ("boolean".equalsIgnoreCase(json.getString("type"))) {
                    boolean v = !StringsUtil.isBlank(setvalue) && !"false".equalsIgnoreCase(setvalue);
                    result.put(name, v);
                } else {
                    setvalue = doFilters(setvalue, json);
                    result.put(name, decode(json.getString("decode"), setvalue));
                }
            }
        }
        return result;
    }

    public static Object getValue(Map<String, Object> context, String name) {
        String[] parts = name.split("\\.");
        Object value = null;
        for (String part : parts) {
            int start = part.indexOf("[");
            if (start == -1) {
                value = value == null ? getValueFromContext(context, part) : getPropertyValue(value, part);
                continue;
            }
            int end = part.indexOf("]", start + 1);
            if (end == -1) return name;
            int index = -1;
            try {
                index = Integer.parseInt(part.substring(start + 1, end));
            } catch (NumberFormatException err) {
                return name;
            }
            part = part.substring(0, start);
            value = value == null ? getValueFromContext(context, part) : getPropertyValue(value, part);
            if (value == null) {
                return null;
            }
            if (JSONArray.class.isAssignableFrom(value.getClass())) {
                value = ((JSONArray) value).get(index);
            } else {
                value = ((Object[]) value)[index];
            }
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    public static Object getValueFromContext(Map<String, Object> context, String name) {
        name = name.trim();
        if (context.containsKey(name)) {
            return context.get(name);
        } else if (context.containsKey("parent")) {
            return getValueFromContext((Map<String, Object>) context.get("parent"), name);
        }
        return null;
    }

    public static String getStringValue(Map<String, Object> context, String name) {
        String[] parts = REUtil.matchs("@.+?@", name);
        if (parts == null || parts.length == 0) return name;
        StringBuilder buffer = new StringBuilder(name);
        for (String part : parts) {
            Object value = getValue(context, part.substring(1, part.length() - 1));
            int start = buffer.indexOf(part);
            if (start > -1) {
                buffer.replace(start, start + part.length(), value == null ? "" : value.toString());
            }
        }
        return buffer.toString();
    }

    @SuppressWarnings("unchecked")
    public static Object getPropertyValue(Object target, String name) {
        if (target == null) return "";
        if (Map.class.isAssignableFrom(target.getClass())) {
            return ((Map<String, ?>) target).get(name);
        } else if (JSONObject.class.isAssignableFrom(target.getClass())) {
            return ((JSONObject) target).get(name);
        } else {
            throw new IllegalArgumentException(String.format("not support from class[%s] get propety.", target.getClass().getName()));
        }
    }

    private static String decode(String method, String text) {
        if ("chuangshi30".equals(method)) {
            String[] parts = text.split("\\\\\\\\");
            StringBuilder buffer = new StringBuilder(parts.length);
            for (String part : parts) {
                if (StringsUtil.isBlank(part)) continue;
                int code = Integer.parseInt(part, 30);
                buffer.append((char) code);
            }
            return buffer.toString();
        } else {
            return text;
        }
    }

    public static boolean isBlank(String value) {
        return value == null || "".equals(value);
    }

    private static class Comparator4JSONObject implements Comparator<String> {
        private JSONObject parent = null;

        Comparator4JSONObject(JSONObject parent) {
            this.parent = parent;
        }

        @Override
        public int compare(String o1, String o2) {

            JSONObject json1 = parent.getJSONObject(o1);
            JSONObject json2 = parent.getJSONObject(o2);

            Integer order1 = json1.getInteger("order");
            Integer order2 = json2.getInteger("order");

            if (order1 == null)
                return -1;
            else if (order2 == null)
                return 1;
            else
                return order1 - order2;
        }
    }

    ;

    private RequestBody buildRequestBody(Map<String, Object> params) {
        FormBody.Builder builder = new FormBody.Builder();
        /**
         * 遍历key
         */
        if (null != params) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                builder.add(entry.getKey(), entry.getValue().toString());

            }
        }
        return builder.build();
    }
}
